/**
 * com.chaeron.GPS - An API for access to GPS devices.
 *
 *
 * Copyright (c) 2001, 2002 Chaeron Corporation,
 * All rights reserved.
 * 
 * Chaeron grants you a non-exclusive license to use, modify and re-distribute
 * this program under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version, provided that this copyright notice and 
 * license appear on all copies of the software.
 * 
 * Software is provided "AS IS," without a warranty of any kind. ALL EXPRESS OR
 * IMPLIED REPRESENTATIONS AND WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE
 * HEREBY EXCLUDED. THE ENTIRE RISK ARISING OUT OF USING THE SOFTWARE IS ASSUMED
 * BY THE LICENSEE. See the GNU General Public License for more details.
 *
 * CHAERON AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY
 * LICENSEE OR ANY THIRD PARTY AS A RESULT OF USING OR DISTRIBUTING SOFTWARE.
 * IN NO EVENT WILL CHAERON OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE,
 * PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL
 * OR PUNITIVE DAMAGES, HOWEVER CAUSED AN REGARDLESS OF THE THEORY OF LIABILITY,
 * ARISING OUT OF THE USE OF OR INABILITY TO USE SOFTWARE, EVEN IF CHAERON HAS
 * BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

 * @version     1.02
 * @author      Andrzej Jan Taramina, Chaeron Corporation
 *              andrzej@chaeron.com
 */

package com.chaeron.GPS.Garmin;

//***************************************************************************
//*
//*     Imports
//*
//***************************************************************************/

import com.chaeron.GPS.*;
import com.chaeron.GPS.event.*;
import com.chaeron.GPS.event.decorator.*;
import com.chaeron.GPS.exception.*;
import com.chaeron.platform.common.*;


//***************************************************************************
//*
//*     GarminPacket Class definition
//*
//***************************************************************************/

/**
 * The GarminPacket class is used to communicate Garmin packets
 *
 */

public class GarminPacket implements Parseable, GPSValue
{
	
	//***************************************************************************
	//*
	//*     Constant definitions
	//*
	//***************************************************************************/
	
	// Buffer offsets
	
	protected final static int      PACKET_START		               	= 0;
	protected final static int      PACKET_ID		              	   	= 1;
	protected final static int		PACKET_LEN						   	= 2;
	protected final static int		PACKET_DATA						   	= 3;
	protected final static int		PACKET_CHECKSUM					   	= -3;
	protected final static int		PACKET_END1						   	= -2;
	protected final static int		PACKET_END2						   	= -1;
	protected final static int		PACKET_HEADER_LEN				   	= 6;
	
	
	// Hex conversion values
	
	private final static char[] HEX_CHARS = {	'0','1','2','3','4','5','6','7','8','9', 'A','B','C','D','E','F'};
	
	// Semicircle to Degrees conversion values
	
	protected final static double	SEMICIRCLE_FACTOR					= (double)( 1L << 31 ) / 180.0d;
	
	
	
	//***************************************************************************
	//*
	//*     Attributes
	//*
	//***************************************************************************/
	
	protected byte[]				packetBuffer						= new byte[ GPS.DEFAULT_PACKETBUFFER_LEN ];
	private int						packetLen							= 0;
	
	private int						methodsImplemented					= 0;
	
	private boolean					parsed								= false;	
	
	//***************************************************************************
	//*
	//*     Static Factory Methods
	//*
	//***************************************************************************/
	
	/**
	 * Factory method to create a subclass of GarminPacket instance from a given packet byte array
	 *
	 * @param	packet		packet data buffer
	 * @param	len			length of packet data
	 * @param	gps			the current gps processor
	 */
	
	static GarminPacket createInstance( GPS gps, byte[] packet, int len )
	{  
		GarminPacket		gp				= null;
		
		switch( packet[ PACKET_ID ] ) {
			case Protocol.GARMIN_PID_ACK:
				gp = new AckGarminPacket( packet, len );
				break;
				
			case Protocol.GARMIN_PID_NACK:
				gp = new NackGarminPacket( packet, len );
				break;
				
			case Protocol.GARMIN_PID_PROTOCOL_ARRAY:
				if( gps.isEventListened( ProtocolArrayGarminPacket.getEventsGenerated() ) ) {
					gp = new ProtocolArrayGarminPacket( packet, len );
				}
				break;
				
			case Protocol.GARMIN_PID_PRODUCT_REQ:
				gp = new ProductReqGarminPacket( packet, len );
				break;
				
			case Protocol.GARMIN_PID_PRODUCT_DATA:
				if( gps.isEventListened( ProductDataGarminPacket.getEventsGenerated() ) ) {
					gp = new ProductDataGarminPacket( packet, len );
				}
				break;
				
			default:
				gp = createLinkSpecificPacket( (GarminGPSImpl)gps, packet, len );
				break;
		}
		
		return( gp );
	}
	
	
	/**
	 *  method to create a subclass of GarminPacket instance based on link protocol type
	 *   
	 *    @param	packet		packet data buffer
	 *    @param	len			length of packet data
	 *    @param	gps			the current gps processor
	 *   
	 */
	
	private static GarminPacket createLinkSpecificPacket( GarminGPSImpl gps, byte[] packet, int len )
	{  
		GarminPacket		gp				= null;
		
		switch( gps.getProtocol().getLinkProtocol() ) {
			case Protocol.LINK_PROTOCOL_L001:
				gp = createLink001Packet( gps, packet, len );
				break;
				
			case Protocol.LINK_PROTOCOL_L002:
				gp = createLink002Packet( gps, packet, len );
				break;
		}
		
		if( gp == null && gps.isEventListened( GPSEvent.GPS_EVENT_GARMIN_PACKET ) ) {
			gp = new GarminPacket( packet, len );		// Unknown so create a generic packet
		}
		
		return( gp );
	}
	
	
	/**
	 *    method to create a subclass of GarminPacket instance based on L001 link protocol
	 *  
	 *   @param	packet		packet data buffer
	 *   @param	len			length of packet data
	 *   @param	gps			the current gps processor
	 *  
	 */
	
	private static GarminPacket createLink001Packet( GarminGPSImpl gps, byte[] packet, int len )
	{  
		GarminPacket		gp				= null;
		
		switch( packet[ PACKET_ID ] ) {
				
			case Protocol.GARMIN_PID_L001_RECORDS:
				if( gps.isEventListened( RecordsGarminPacket.getEventsGenerated() ) ) {
					gp = new RecordsGarminPacket( gps.getProtocol(), packet, len );
				}
				break;
				
			case Protocol.GARMIN_PID_L001_WAYPOINT:
				if( Compile.INCLUDE_GARMIN_DOWNLOAD_WAYPOINTS ) {
					if( gps.isEventListened( WaypointGarminPacket.getEventsGenerated() ) ) {
						gp = new WaypointGarminPacket( gps.getProtocol(), packet, len, Protocol.GARMIN_PID_WAYPOINT );
					}
				}
				break;
				
			case Protocol.GARMIN_PID_L001_ROUTE_WAYPOINT:
				if( Compile.INCLUDE_GARMIN_DOWNLOAD_ROUTES ) {
					if( gps.isEventListened( WaypointGarminPacket.getEventsGenerated() ) ) {
						gp = new WaypointGarminPacket( gps.getProtocol(), packet, len, Protocol.GARMIN_PID_ROUTE_WAYPOINT );
					}
				}
				break;
				
			case Protocol.GARMIN_PID_L001_ROUTE_HDR:
				if( Compile.INCLUDE_GARMIN_DOWNLOAD_ROUTES ) {
					if( gps.isEventListened( RouteHeaderGarminPacket.getEventsGenerated() ) ) {
						gp = new RouteHeaderGarminPacket( gps.getProtocol(), packet, len );
					}
				}
				break;
				
			case Protocol.GARMIN_PID_L001_ROUTE_LINK:
				if( Compile.INCLUDE_GARMIN_DOWNLOAD_ROUTES ) {
					if( gps.isEventListened( RouteLinkDataGarminPacket.getEventsGenerated() ) ) {
						gp = new RouteLinkDataGarminPacket( gps.getProtocol(), packet, len );
					}
				}
				break;
				
			case Protocol.GARMIN_PID_L001_TRACK_HDR:
				if( Compile.INCLUDE_GARMIN_DOWNLOAD_TRACKLOGS ) {
					if( gps.isEventListened( TrackHeaderGarminPacket.getEventsGenerated() ) ) {
						gp = new TrackHeaderGarminPacket( gps.getProtocol(), packet, len );
					}
				}
				break;
				
			case Protocol.GARMIN_PID_L001_TRACK_POINT:
				if( Compile.INCLUDE_GARMIN_DOWNLOAD_TRACKLOGS ) {
					if( gps.isEventListened( TrackPointGarminPacket.getEventsGenerated() ) ) {
						gp = new TrackPointGarminPacket( gps.getProtocol(), packet, len );
					}
				}
				break;
				
			case Protocol.GARMIN_PID_L001_XFER_COMPLETE:
				if( gps.isEventListened( XferCompleteGarminPacket.getEventsGenerated() ) ) {
					gp = new XferCompleteGarminPacket( gps.getProtocol(), packet, len );
				}
				break;
				
			case Protocol.GARMIN_PID_L001_PVT:
				if( Compile.INCLUDE_GARMIN_PACKET_PVT ) {
					if( gps.isEventListened( PVTGarminPacket.getEventsGenerated() ) ) {
						gp = new PVTGarminPacket( packet, len );
					}
				}
				break;
		}
		
		return( gp );
	}
	
	
	/**
	 *  method to create a subclass of GarminPacket instance based on L002 link protocol
	 *
	 * @param	packet		packet data buffer
	 * @param	len			length of packet data
	 * @param	gps			the current gps processor
	 *
	 */
	
	private static GarminPacket createLink002Packet( GarminGPSImpl gps, byte[] packet, int len )
	{  
		GarminPacket		gp				= null;
		
		switch( packet[ PACKET_ID ] ) {
				
			case Protocol.GARMIN_PID_L002_RECORDS:
				if( gps.isEventListened( RecordsGarminPacket.getEventsGenerated() ) ) {
					gp = new RecordsGarminPacket( gps.getProtocol(), packet, len );
				}
				break;
				
			case Protocol.GARMIN_PID_L002_WAYPOINT:
				if( Compile.INCLUDE_GARMIN_DOWNLOAD_WAYPOINTS ) {
					if( gps.isEventListened( WaypointGarminPacket.getEventsGenerated() ) ) {
						gp = new WaypointGarminPacket( gps.getProtocol(), packet, len, Protocol.GARMIN_PID_WAYPOINT );
					}
				}
				break;
				
			case Protocol.GARMIN_PID_L002_ROUTE_WAYPOINT:
				if( Compile.INCLUDE_GARMIN_DOWNLOAD_ROUTES ) {
					if( gps.isEventListened( WaypointGarminPacket.getEventsGenerated() ) ) {
						gp = new WaypointGarminPacket( gps.getProtocol(), packet, len, Protocol.GARMIN_PID_ROUTE_WAYPOINT );
					}
				}
				break;
				
			case Protocol.GARMIN_PID_L002_ROUTE_HDR:
				if( Compile.INCLUDE_GARMIN_DOWNLOAD_ROUTES ) {
					if( gps.isEventListened( RouteHeaderGarminPacket.getEventsGenerated() ) ) {
						gp = new RouteHeaderGarminPacket( gps.getProtocol(), packet, len );
					}
				}
				break;
				
			case Protocol.GARMIN_PID_L002_XFER_COMPLETE:
				if( gps.isEventListened( XferCompleteGarminPacket.getEventsGenerated() ) ) {
					gp = new XferCompleteGarminPacket( gps.getProtocol(), packet, len );
				}
				break;
		}
		
		return( gp );
	}
	
	
	//***************************************************************************
	//*
	//*     Constructor Methods
	//*
	//***************************************************************************/
	
	/**
	 * Create a GarminPacket instance from a given packet byte array
	 *
	 * @param	packet		packet data buffer
	 * @param	len			length of packet data
	 */
	
	protected GarminPacket( byte[] packet, int len )
	{  
		Vm.copyArray( packet, 0, packetBuffer, 0, len );
		
		packetLen = len;
	}
	
	
	/**
	 * Create a GarminPacket instance for a given type and data byte array
	 *
	 * @param	type		packet type (PID)
	 * @param	data		data buffer
	 * @param	dataLen		length of data
	 */
	
	protected GarminPacket( byte type, byte[] data, int dataLen )
	{  
		packetLen = dataLen + PACKET_HEADER_LEN;
		
		packetBuffer[ 0 ] = GarminGPSImpl.BEGIN_PACKET;
		packetBuffer[ PACKET_ID ] = type;
		packetBuffer[ PACKET_LEN ] = (byte)dataLen;
		
		Vm.copyArray( data, 0, packetBuffer, PACKET_DATA, dataLen );
		
		setChecksum();
		
		packetBuffer[ packetLen + PACKET_END1 ] = GarminGPSImpl.END_PACKET1;
		packetBuffer[ packetLen + PACKET_END2 ] = GarminGPSImpl.END_PACKET2;
	}
	
	
	//***************************************************************************
	//*
	//*     Packet Processing Methods (overriden by subclasses)
	//*
	//***************************************************************************/
	
	/**
	 * Process the packet
	 *
	 * @param	gps			the GPS object that requested the packet to be processed
	 */
	
	void process( GPS gps )
	{
		gps.postGPSEvent( new GPSEvent( GPSEvent.GPS_EVENT_UNKNOWN_DATA, this ) );
	}
	
	
	//***************************************************************************
	//*
	//*     Accessor Methods
	//*
	//***************************************************************************/
	
	
	/**
	 * Get Packet Buffer 
	 *
	 * @return		packet buffer
	 */
	
	byte[] getPacketBuffer()
	{
		return( packetBuffer );   
	}
	
	
	/**
	 * Get Packet Buffer Length
	 *
	 * @return		packet buffer length
	 */
	
	int getPacketBufferLength()
	{
		return( packetLen );   
	}
	
	
	/**
	 * Set the type  
	 *
	 * @param	type 	packetID type
	 */
	
	protected void setPacketIDByte( byte type )
	{
		packetBuffer[ PACKET_ID ] = type;
	}
	
	
	/**
	 * Get the type  
	 *
	 * @return		type
	 */
	
	byte getPacketIDByte()
	{
		return( packetBuffer[ PACKET_ID ] );   
	}
	
	
	/**
	 * Get the type  
	 *
	 * @return		type
	 */
	
	public short getPacketID()
	{
		return( (short)( ( packetBuffer[ PACKET_ID ] + 256 ) & 0xFF ) );   
	}
	
	
	/**
	 * Get the PacketID as a displayable string 
	 *
	 * @param	protocol	protocol object
	 * @return		PacketID as string
	 */
	
	public String getPacketIDString( Protocol protocol )
	{
		return( protocol.getPacketIDString( getPacketIDByte() ) );   
	}
	
	
	/**
	 * Get the datalength  
	 *
	 * @return	data length
	 */
	
	public int getDataLength()
	{
		return( ( packetBuffer[ PACKET_LEN ] + 256 ) & 0xFF );   
	}
	
	
	/**
	 * Validate the packet length 
	 *
	 * @return		is valid?
	 */
	
	public boolean validateLength()
	{
		return( getPacketBufferLength() == getDataLength() + PACKET_HEADER_LEN );   
	}
	
	
	/**
	 * Get a data byte
	 *
	 * @param		index		index of data byte to retrieve
	 * @return					data byte
	 */
	
	public byte getDataByte( int index )
	{
		return( packetBuffer[ PACKET_DATA + index ] );   
	}
	
	
	/**
	 * Get the data bytes
	 *
	 * @param		index		index of data bytes to retrieve
	 * @param		len			number of data bytes to retrieve
	 * @return					data byte
	 */
	
	public byte[] getDataBytes( int index, int len )
	{
		byte[] bytes		= new byte[ len ];
		
		for( int i = 0; i < len; i++ ) {
			bytes[ i ] = packetBuffer[ PACKET_DATA + index + i ];
		}
		
		return( bytes );   
	}
	
	
	/**
	 * Get the checksum 
	 *
	 * @return		checksum
	 */
	
	public byte getChecksum()
	{
		return( packetBuffer[ packetLen + PACKET_CHECKSUM ] );   
	}
	
	
	/**
	 * Validate the checksum 
	 *
	 * @return		is valid?
	 */
	
	public boolean validateChecksum()
	{
		return( getChecksum() == calcChecksum() );   
	}
	
	
	/**
	 * Set the checksum  (calculated from the data)
	 */
	
	public void setChecksum()
	{
		packetBuffer[ packetLen + PACKET_CHECKSUM ] = calcChecksum();
	}
	
	
	/**
	 * Calculate the checksum from the data value
	 *
	 * @return		the calculated checksum
	 */
	
	public byte calcChecksum()
	{
		int 	i;
		int		checksum = 0;
		int		len;
		byte[]	cs	= new byte[ 1 ];
		
		len = getDataLength();
		
		checksum = getPacketIDByte() + getDataLength();
		
		for( i = 0; i < len; i++ ) {
			checksum += getDataByte( i );
		}
		
		checksum = ~checksum + 1;
		
		cs[ 0 ] = (byte)checksum;			// Superwaba workaround
		
		return( cs[ 0 ] );
	}
	
	
	/**
	 * Were the data fields parsed already? 
	 *
	 * @return		true if parsed already
	 */
	
	public boolean isParsed()
	{  
		return( parsed );
	}
	
	
	/**
	 * Parse the data fields.
	 *
	 * @return		This method should return a standard error code or GPS.ERROR_OK
	 * @throws GPSException
	 */
	
	public void parse() throws GPSException
	{  
		if( !parsed ) {
			parseData();
			parsed = true;
		}
	}
	
	
	/**
	 * Parse the data fields. Overriden by subclasses
	 *
	 * @return		This method should return a standard error code or GPS.ERROR_OK
	 * @throws GPSException
	 */
	
	protected void parseData() throws GPSException
	{  
		throw( new GPSFunctionUnavailableException() );
	}
	
	
	/**
	 * Does the object contain valid data?
	 *
	 * @return		Does the object contain valid data?
	 */
	
	public boolean isDataValid()
	{  
		return( true );
	}
	
	
	/**
	 * Get events generated. Overriden by subclasses
	 *
	 * @return		return a valid event type(s) that will be generated by the packet subclass
	 */
	
	static int getEventsGenerated()
	{  
		return( 0 );
	}
	
	
	/**
	 * Return a string representation of the packet (in hex)
	 *
	 * @return		string representation of the packet (in hex)
	 */
	
	public String toString()
	{  
		StringBuffer		str		=  new StringBuffer();
		int					i;
		byte				data;
		
		for( i = 0; i < packetLen; i++ ) {
			data = packetBuffer[ i ];
			str.append( HEX_CHARS[ ( ( data + 256 ) & 0xf0 ) >> 4 ] );
			str.append( HEX_CHARS[ ( data + 256 ) & 0x0f ] );
		}
		
		return( str.toString() );
	}
	
	
	//***************************************************************************
	//*
	//*     Packet Data Conversion Methods
	//*
	//***************************************************************************/
	
	/**
	 * Return a Garmin boolean (Java boolean) from the packet data at the given index
	 *
	 * @return		a Garmin boolean (Java boolean)
	 */
	
	protected boolean getGarminBoolean( int index )
	{
		return( getDataByte( index ) != 0 );
	}
	
	
	/**
	 * Return a Garmin int (Java short) from the packet data at the given index
	 *
	 * @return		a Garmin int (Java short)
	 */
	
	protected short getGarminInt( int index )
	{
		int		low;
		int		high;
		int		sign;
		
		low 	= ( (int)getDataByte( index ) + 256 ) & 0xFF;
		high	= ( ( (int)getDataByte( index + 1 ) + 256 ) & 0x7F ) << 8;
		sign 	= ( ( (int)getDataByte( index + 1 ) + 256 ) & 0x80 ) << 8;
		
		return( (short)( sign | low | high ) );
	}
	
	
	/**
	 * Return a Garmin word (unsigned, Java int) from the packet data at the given index
	 *
	 * @return		a Garmin word (unsigned, Java int)
	 */
	
	protected int getGarminWord( int index )
	{
		int		low;
		int		high;
		
		low 	= ( (int)getDataByte( index ) + 256 ) & 0xFF;
		high	= ( ( (int)getDataByte( index + 1 ) + 256 ) & 0xFF ) << 8;
		
		return( low | high );
	}
	
	
	/**
	 * Return a Garmin long (Java int) from the packet data at the given index
	 *
	 * @return		a Garmin long (Java int)
	 */
	
	protected int getGarminLong( int index )
	{
		int		a; 
		int		b; 
		int		c; 
		int		d; 
		int		sign;
		
		a 		= ( (int)getDataByte( index ) + 256 ) & 0xFF;
		b		= ( ( (int)getDataByte( index + 1 ) + 256 ) & 0xFF ) << 8;
		c		= ( ( (int)getDataByte( index + 2 ) + 256 ) & 0xFF ) << 16;
		d		= ( ( (int)getDataByte( index + 3 ) + 256 ) & 0x7F ) << 24;
		sign	= ( ( (int)getDataByte( index + 3 ) + 256 ) & 0x80 ) << 24;
		
		return( sign | a | b | c | d );
	}
	
	
	/**
	 * Return a Garmin longword (unsigned, Java long) from the packet data at the given index
	 *
	 * @return		a Garmin longword (unsigned, Java long)
	 */
	
	protected long getGarminLongword( int index )
	{
		long	a; 
		long	b;
		long	c; 
		long	d; 
		
		a 		= ( (long)getDataByte( index ) + 256 ) & 0xFF;
		b		= ( ( (long)getDataByte( index + 1 ) + 256 ) & 0xFF ) << 8;
		c		= ( ( (long)getDataByte( index + 2 ) + 256 ) & 0xFF ) << 16;
		d		= ( ( (long)getDataByte( index + 3 ) + 256 ) & 0xFF ) << 24;
		
		return( a | b | c | d );
	}
	
	
	/**
	 * Return a Garmin Xlong (Java long) from the packet data at the given index
	 *
	 * @return		a Garmin Xlong (Java long)
	 */
	
	protected long getGarminXLong( int index )
	{
		long		a; 
		long		b; 
		long		c; 
		long		d; 
		long		e;
		long		f;
		long		g;
		long		h;
		long		sign;
		
		a 		= ( (long)getDataByte( index ) + 256 ) & 0xFF;
		b		= ( ( (long)getDataByte( index + 1 ) + 256 ) & 0xFF ) << 8;
		c		= ( ( (long)getDataByte( index + 2 ) + 256 ) & 0xFF ) << 16;
		d		= ( ( (long)getDataByte( index + 3 ) + 256 ) & 0xFF ) << 24;
		e		= ( ( (long)getDataByte( index + 4 ) + 256 ) & 0xFF ) << 32;
		f		= ( ( (long)getDataByte( index + 5 ) + 256 ) & 0xFF ) << 40;
		g		= ( ( (long)getDataByte( index + 6 ) + 256 ) & 0xFF ) << 48;
		h		= ( ( (long)getDataByte( index + 7 ) + 256 ) & 0x7F ) << 56;
		sign	= ( ( (long)getDataByte( index + 7 ) + 256 ) & 0x80 ) << 56;				
		
		return( sign | a | b | c | d | e | f | g | h );
	}
	
	
	/**
	 * Return a Garmin float (Java float) from the packet data at the given index
	 *
	 * @return		a Garmin float (Java float)
	 */
	
	protected float getGarminFloat( int value )
	{
		return( Convert.intBitsToFloat( getGarminLong( value ) ) );
	}
	
	
	/**
	 * Return a Garmin double from the packet data at the given index
	 *
	 * @return		a Garmin double 
	 */
	
	protected double getGarminDouble( int index )
	{
		return( Convert.longBitsToDouble( getGarminXLong( index ) ) );
	}
	
	
	/**
	 * Return a Garmin String (Java String) from the packet data at the given index
	 *
	 * @return		a Garmin String (Java String)
	 */
	
	protected String getGarminString( int index )
	{
		String			value			= "";
		int				i;
		StringBuffer	buf 			= new StringBuffer();
		char			chr;
		
		for( i = index; i < getDataLength(); i ++ ) {
			chr = (char)getDataByte( i );
			if( chr == 0 ) {
				value = buf.toString();
				buf.setLength( 0 );
				break;
			} else {
				buf.append( chr );
			}
		}
		
		return( value );
	}
	
	
	/**
	 * Convert Semicircles to Degrees
	 *
	 * @param	semicircles		Semicircles
	 * @return					degrees
	 */
	
	public static double convertSemicirclesToDegrees( int semicircles )
	{  
		return( (double)semicircles / SEMICIRCLE_FACTOR );
	}
	
	
	/**
	 * Convert Degrees to Semicircles
	 *
	 * @param	degrees		degrees
	 * @return				radians
	 */
	
	public static int convertDegreesToSemicircles( double degrees )
	{  
		return( (int)(degrees * SEMICIRCLE_FACTOR) );
	}
	
	
	//***************************************************************************
	//*
	//*     GPSValue Methods
	//*
	//***************************************************************************/
	
	/**
	 * Get the Methods Implemented flags
	 *
	 * @return		methods implemented flags
	 */
	
	public int getMethodsImplemented() 
	{
		return( methodsImplemented );
	}
	
	/**
	 * Set the methodsImplemented flags
	 *
	 * @param	flags	field flags
	 */
	
	public void setMethodsImplemented( int flags ) 
	{
		methodsImplemented = flags;
	}
	
	/**
	 * Test if a method is implemented
	 *
	 * @param	methodFlag	method to test
	 */
	
	public boolean isMethodImplemented( int methodFlag )
	{
		return( ( methodsImplemented & methodFlag ) != 0 );
	}
	
	/**
	 * Return this Event Value as XML
	 *
	 * @return Event Value as XML
	 */
	
	public String toXML() 
	{
		String		tag  = "";
		
		if( Compile.INCLUDE_EVENT_TOXML ) {
			tag = GPSEventDecorator.genXMLValueTag( GPSXMLTag.XML_TAG_GARMINPACKET, toString() );
		}
		
		return( tag );
	}
	
	/**
	 * Return this Event Value as XML
	 *
	 * @param declaration include XML Declaration at start?
	 * @param namespace include XML namespace at start?
	 * @return Event Value as XML
	 */
	
	public String toXML( boolean declaration, boolean namespace )
	{
		StringBuffer 	tag		= new StringBuffer();
		
		if( Compile.INCLUDE_EVENT_TOXML ) {
			if( declaration ) {
				tag.append( GPSXMLTag.XML_DECLARATION );
			}
			
			tag.append( GPSEventDecorator.genXMLTag( GPSXMLTag.XML_TAG_GARMINPACKET, null, toString(), true, true, namespace ) );
		}
		
		return( tag.toString() );
	}
	
	/**
	 * Write this Event Value as XML to a catalog
	 *
	 * @param catalog	catalog to write to.
	 */
	
	public void toXML( Catalog catalog ) 
	{
		if( Compile.INCLUDE_EVENT_TOXML && Compile.INCLUDE_EVENT_SAVETOCATALOG ) {
			String	tag;
		
			tag = GPSEventDecorator.genXMLValueTag( GPSXMLTag.XML_TAG_GARMINPACKET, toString() );
			GPSEventDecorator.writeString( catalog, tag );
		}
	}
	
	/**
	 * Write this Event Value as XML to a catalog
	 *
	 * @param catalog	catalog to write to.
	 * @param declaration include XML Declaration at start?
	 * @param namespace include XML namespace at start?
	 */
	
	public void toXML( Catalog catalog, boolean declaration, boolean namespace ) 
	{
		if( Compile.INCLUDE_EVENT_TOXML && Compile.INCLUDE_EVENT_SAVETOCATALOG  ) {
			String	tag;
			
			if( declaration ) {
				tag = GPSXMLTag.XML_DECLARATION;
				GPSEventDecorator.writeString( catalog, tag );
			}
			
			tag = GPSEventDecorator.genXMLTag( GPSXMLTag.XML_TAG_GARMINPACKET, null, toString(), true, true, namespace );
			GPSEventDecorator.writeString( catalog, tag );
		}
	}
	
}
